package controller;

import java.util.ArrayList;
import model.Checkerboard;

/**
 *
 * @author Martin
 */
public class Move {

    static ArrayList<ArrayList<Move>> listCalculatedMovesAllPieces = new ArrayList<ArrayList<Move>>(); // list of calculated moves for current situation
    private Checkerboard checkerboard;
    // Position of the piece which is this move sequence for:
    //      We need to remove that piece virtually from the checkerboard
    //      when calculating its moves
    // !! parent Move sets callingX, Y to its following
    private int callingX;
    private int callingY;
    private int startX;
    private int startY;
    private int destX;
    private int destY;
    //private int currentPiece = Checkerboard.INVALID; //necessary because of multiple jumps - this will differ from model.
    // !! currentPiece = getPiece(callingX,callingY);
    // List of possible moves from position possibleMovesPositionX, possibleMovesPositionY
    private ArrayList<Move> listValidFollowing = new ArrayList<Move>(0); // Majority of Moves has no following move
    private int nbCapturesMax = 0; // Depth of the Move-(sub)tree
    // list of pieces position to remove when a round finished
    // contains all of pieces captured BEFORE (not following) and by this move
    private ArrayList<int[]> listCapturedPieces = new ArrayList<int[]>();

    public Move(Checkerboard checkerboard, int callingX, int callingY, int startX, int startY, int destX, int destY) {
        this.checkerboard = checkerboard;
        this.callingX = callingX;
        this.callingY = callingY;
        this.startX = startX;
        this.startY = startY;
        this.destX = destX;
        this.destY = destY;
    }

    public Move() {
    }

    /**
     * Dummy Move object creation for sake of comparison.
     * Example:<br>
     * <code>ArrayList array = new ArrayList();<br>
     * Move aMoveCreatedBefore = new Move();<br>
     * array.add(aMoveCreatedBefore);<br>
     * System.out.println(array.contains(new Move(1, 2)));
     * </code>
     * 
     * @param startX horizontal coordinate
     * @param startY vertical coordinate
     */
    public Move(Checkerboard checkerboard, int startX, int startY) {
        this.checkerboard = checkerboard;
        this.startX = startX;
        this.startY = startY;
        this.callingX = -1;
        this.callingY = -1;
        this.destX = -1;
        this.destY = -1;
    }

    private Move(Checkerboard checkerboard, int startX, int startY, int destX, int destY) {
        this.checkerboard = checkerboard;
        this.startX = startX;
        this.startY = startY;
        this.callingX = -1;
        this.callingY = -1;
        this.destX = destX;
        this.destY = destY;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Move) {
            Move m = (Move) obj;
            return (m.startX == this.startX
                    && m.startY == this.startY
                    && (m.callingX == this.callingX || this.callingX == -1 || m.callingX == -1)
                    && (m.callingY == this.callingY || this.callingY == -1 || m.callingY == -1)
                    && (m.getDestX() == this.getDestX() || this.getDestX() == -1 || m.getDestX() == -1)
                    && (m.getDestY() == this.getDestY() || this.getDestY() == -1 || m.getDestY() == -1)
                    && m.checkerboard == this.checkerboard);
        }
        return false;
    }

    // Code proposé par NetBeans
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 53 * hash + (this.checkerboard != null ? this.checkerboard.hashCode() : 0);
        hash = 53 * hash + this.startX;
        hash = 53 * hash + this.startY;
        hash = 53 * hash + this.callingX;
        hash = 53 * hash + this.callingY;
        return hash;
    }

    @Override
    public String toString() {
        return "Move (" + startX + ", " + startY + ") >> (" + getDestX() + ", " + getDestY() + ")";
    }

    public static boolean isCalculated(Checkerboard checkerboard, int x, int y) {
        return -1 == indexOf(checkerboard, x, y);
    }

    private static int indexOf(Checkerboard checkerboard, int x, int y) {
        Move position = new Move(checkerboard, x, y);
        for (int i = 0; i < listCalculatedMovesAllPieces.size(); i++) {
            if (listCalculatedMovesAllPieces.get(i).get(0).equals(position)) {
                return i;
            }
        }
        return -1;
    }

    public static Move getMove(Checkerboard checkerboard, int startX, int startY, int destX, int destY) {
        ArrayList<Move> moves = getMoves(checkerboard, startX, startX);
        for (int i = 0; i < moves.size(); i++) {
            Move move = moves.get(i);
            if (move.equals(new Move(checkerboard, startX, startY, destX, destY))) {
                return move;
            }
        }
        return null;

    }

    /**
     * Checks for possible moves for the piece on the specified position
     * @param x horizontal position
     * @param y vertical position
     * @return list of possible moves ot null if no moes
     */
    public static ArrayList<Move> getMoves(Checkerboard checkerboard, int x, int y) {
        return getMoves(checkerboard, x, y, x, y,
                new ArrayList<int[]>(2) // Let's suppose that there is about 2 pieces captured by one piece
                );
    }

    /**
     * Checks recursively for possible moves for the piece (specified by startX/Y) on the specified position
     * @param checkerboard
     * @param callingX horizontal position of the reference piece supposed EMPTY for this method
     * @param callingY horizontal position of the reference piece supposed EMPTY for this method
     * @param startX horizontal position of the reference piece supposed occupied by this piece
     * @param startY vertical position of the reference piece supposed occupied by this piece
     * @param listCapturedPiecesBefore list of pieces being captured before
     * @return list of possible moves ot null if no moves
     */
    public static ArrayList<Move> getMoves(Checkerboard checkerboard, int callingX, int callingY, int startX, int startY, ArrayList<int[]> listCapturedPiecesBefore) {

        int piece = checkerboard.getPiece(callingX, callingY);
        int index = indexOf(checkerboard, startX, startY);
        if (index >= 0) { //is in calculated list
            // Let's return the cached value
            return listCalculatedMovesAllPieces.get(index);
        } else {
            ArrayList<Move> listReturn = new ArrayList<Move>(2); // usually two possibilities for a piece
            if (piece == Checkerboard.EMPTY) {
                // If there is no piece
                return null;
            }
            // Moves with capturing
            // FRENCH DRAUGHS : in every direction
            // Analyze:
            int aPhase;
            // Phase 1=leading EMPTY places (for queens)
            //       2=enemy's piece
            //       3=after EMPTY place
            //       4=after other EMPTY places
            //       0=stop processing = error
            for (int rotation = 0; rotation < 4; rotation++) {
                int h = 1 - 2 * (rotation % 2); // horizontal coef 0=>+1, 1=>-1, 2=>+1, 3=>-1
                int v = 1 - 2 * (rotation / 2); // vertical coef 0=>+1, 1=>+1, 2=>-1, 3=>-1
                // Rotation legend (graphical meaning of the loop):
                // NW=3 \/ 2=NE
                // SW=1 /\ 0=SE

                if (Checkerboard.isDame(piece)) {
                    // Initialize for Dame
                    aPhase = 1; // any EMPTY squares are OK
                } else {
                    // initialize for Pawn
                    aPhase = 2;
                }

                // Matching sequence PAWN:  "0*X(0).*" - then add position of (0)
                // Matching sequence DAME:  "X(0).*" - then add position of (0)
                ArrayList sequence = checkerboard.getSequenceWithoutCalling(callingX, callingY, startX, startY, h, v);
                int removeX = -1, removeY = -1; // inicialized in State 2
                for (int i = 0; i < sequence.size() && aPhase != 0; i++) {
                    int aPiece = (Integer) sequence.get(i);
                    if (Checkerboard.getPlayer(aPiece) == Checkerboard.getPlayer(piece)) {
                        aPhase = 0;
                    }
                    switch (aPhase) {

                        // State 1: if EMPTY space, stay in (1), read another char
                        //          if another character, pass to (2) without reading new char
                        case 1:
                            if (aPiece == Checkerboard.EMPTY) {
                                /* // Unnecessery because the Pawn is initialized in 2 - see what solution is better
                                if (!Checkerboard.isDame(piece)) {
                                // if piece is pawn - no EMPTY space admitted
                                aPhase = 0; // STOP
                                }
                                 */
                                break; // Read another character / test condition
                            } else {
                                aPhase = 2; // Continue in phase 2 withoud reading another char
                            }

                        // State 2: if enemy's piece here, pass to (3), read another char
                        //          if another character, STOP (0)
                        case 2:
                            removeX = startX + h * (i + 1);
                            removeY = startY + v * (i + 1);
                            // To avoid infinite loops: verify aabsence of the newly captured piece in already captured pieces
                            if (Checkerboard.getPlayer(aPiece) == Checkerboard.getEnemy(Checkerboard.getPlayer(piece))
                                    && !listCapturedPiecesBefore.contains(new int[]{removeX, removeY})) {
                                aPhase = 3;
                            } else {
                                aPhase = 0; // STOP
                            }
                            break; // Read another character / test condition

                        // State 3: if EMPTY space, ACCEPTED, stay in (3), read another char (if Dame)
                        //          if another character, STOP (0)
                        case 3:
                            if (aPiece == Checkerboard.EMPTY) {
                                // OK add to the list
                                Move theMove = new Move(checkerboard, startX, startY, startX + h * (i + 1), startY + v * (i + 1));
                                theMove.callingX = callingX;
                                theMove.callingY = callingY;
                                theMove.listCapturedPieces.addAll(listCapturedPiecesBefore);
                                theMove.listCapturedPieces.add(new int[]{removeX, removeY});
                                System.out.println(theMove);
                                listReturn.add(theMove);
                                ///////////////////////////////////////////////////////////////////////////
                                //  NASTY RECURSION HERE !
                                ///////////////////////////////////////////////////////////////////////////
                                // Send a copy of list, not a reference to this one
                                theMove.listValidFollowing = getMoves(
                                        checkerboard, callingX, callingY, theMove.getDestX(), theMove.getDestY(),
                                        new ArrayList<int[]>(theMove.listCapturedPieces));

                                if (Checkerboard.isDame(piece)) {
                                    break; // Stay in the state 3
                                }
                            }
                            aPhase = 0;
                            break; // Read another character
                    }
                }
            }



            // If there is no forced moves, check for others AND no captures before
            if (listCapturedPiecesBefore.isEmpty() && callingX == startX && callingY == startY) {
                // Moves without capturing
                int distance = 0;
                //TODO: invert loops outer <-> inner
                //TODO: see if a iterator would be more elegant
                do {
                    distance++; // Start at distance = 0 + 1
                    // search in 4 directions
                    boolean[] stop = new boolean[]{false, false, false, false};
                    int h, v;
                    for (int i = 0; i < 4; i++) {
                        h = 1 - 2 * (i % 2); // horizontal coef 0=>+1, 1=>-1, 2=>+1, 3=>-1
                        v = 1 - 2 * (i / 2); // vertical coef 0=>+1, 1=>+1, 2=>-1, 3=>-1
                        // NW=3 \/ 2=NE
                        // SW=1 /\ 0=SE
                        if ((stop[i] == false)
                                && (checkerboard.getPiece(startX + h * distance, startY + v * distance) == Checkerboard.EMPTY)
                                && ((piece != Checkerboard.P1_PAWN || v != -1) && (piece != Checkerboard.P2_PAWN || v != 1))) {
                            Move theMove = new Move(checkerboard, callingX, callingY);
                            theMove.callingX = callingX;
                            theMove.callingY = callingY;
                            theMove.destX = startX + h * distance;
                            theMove.destY = startY + v * distance;

                            listReturn.add(theMove);
                        } else {
                            stop[i] = true;
                        }
                    }
                } // repeat "until discovery it is a pawn" (1 time) or until we are searching out of bounds
                while (distance < checkerboard.getSize() && Checkerboard.isDame(piece));
            }
            if (!listReturn.isEmpty())
                listCalculatedMovesAllPieces.add(listReturn);
            show(listReturn);
            return listReturn;
        }
    }

    public static void show(ArrayList <Move> list) {
       /* if (list.size()>0)
            System.out.println("* possible moves:");
        else
            System.out.println("# no moves");*/

        for (int i = 0; i < list.size(); i++)
            System.out.println(list.get(i));
    }

    /**
     * Checks whether the move from x1, y1 to x2, y2 is legal
     * @param startX horizontal offset source square
     * @param startY vertical offset source square
     * @param destX horizontal offset destination square
     * @param destY vertical offset destination square
     * @return true if the move is allowed, false if prohibited
     */
    public static boolean checkMove(Checkerboard checkerboard, int startX, int startY, int destX, int destY) {

        // Verify coordinates not out of bounds and not the same
        if (!checkerboard.checkBorders(startX, startY) || !checkerboard.checkBorders(destX, destY) || (startX == destX && startY == destY)) {
            return false;
        }

        // Get all possible moves
        ArrayList<Move> listMoves = getMoves(checkerboard, startX, startY);
        System.out.println(checkerboard + " " + +startX + " " + startY + " " + destX + " " + destY);
        return listMoves.contains(new Move(checkerboard, startX, startY, destX, destY));
    }

    /**
     * Manipulates the piece specified by the position x1, y1
     * towards x2, y2 if the move valid; Updates chackerboard.currentMove to point to current Move (Interesting for captured pieces)
     * @param x1 horizontal offset source square
     * @param y1 vertical offset source square
     * @param x2 horizontal offset destination square
     * @param y2 vertical offset destination square
     * @return false if move forbidden, true if the move's successfully done
     */
    public static boolean move(Checkerboard checkerboard, int x1, int y1, int x2, int y2) {
        // Move the piece to the new position
        int piece = checkerboard.getPiece(x1, y1);
        System.out.println("ASK: " + cCheckerboard.pieceToChar(piece) + " (" + x1 + ", " + y1 + " >> " + x2 + ", " + y2 + ")");

        ArrayList<Move> listCurrentMoves = getMoves(checkerboard, x1, y1);
        checkerboard.setListCurrentMoves(listCurrentMoves);
        int index = listCurrentMoves.indexOf(new Move(checkerboard, x1, y1, x2, y2));
        if (index == -1) // The move is not valid
        {
            return false;
        }

        Move currentMove = listCurrentMoves.get(index);
        checkerboard.setCurrentMove(currentMove); // Current state reference
        checkerboard.setPiece(x2, y2, piece);
        checkerboard.setPiece(x1, y1, Checkerboard.EMPTY);
        System.out.println("DONE: " + cCheckerboard.pieceToChar(piece) + " (" + x1 + ", " + y1 + " >> " + x2 + ", " + y2 + ")");


        // Find the move in structures

        // Add to a list all of captured pieces (class variable)
        // If there is no more compulsory moves, take the captured pieces off the table
        if (!currentMove.listValidFollowing.isEmpty()) {
            // Remove captured pieces
            for (int i = 0; i < currentMove.listCapturedPieces.size(); i++) {
                int[] position = currentMove.listCapturedPieces.get(i);
                int x = position[0];
                int y = position[1];
                checkerboard.setPiece(x, y, Checkerboard.EMPTY);
            }
            listCalculatedMovesAllPieces.clear();
            //checkerboard.setListCurrentMoves(null);
        } else {
            // Multiple jump
            checkerboard.setListCurrentMoves(currentMove.listValidFollowing);
        }
        return true;
    }

    /**
     * returns index of specific coordinates in ArrayList arrayMoves
     * @param arrayMoves ArrayList with int[] coodrinates
     * @param x horizontal offset square
     * @param y vertical offset square
     * @return index in arrayMoves for {x, y}
     */
    private int indexOfMove(ArrayList<int[]> arrayMoves, int x, int y) {
        for (int i = 0; i < arrayMoves.size(); i++) {
            int[] is = arrayMoves.get(i);
            if (is[0] == x && is[1] == y) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @return the destX
     */
    public int getDestX() {
        return destX;
    }

    /**
     * @return the destY
     */
    public int getDestY() {
        return destY;
    }
}
